#pragma once

#include "./menu.hpp"


namespace beef {
    /**
     * @brief SpinBox 类可用于选择数字或选项列表中的值，相比MenuPage 简化了显示功能，多个Spinbox 组合使用时优化资源占用。
     *
     */
    class SpinBox {
       private:
        ConstantStrArrayPtr _list = nullptr;  // 选项列表

        SpinBox* _next = nullptr;  // 下一个SpinBox，用于自动处理数字进位，如果this 代表个位数，则next 应该表示十位

        PointType _x;
        PointType _y;  // SpinBox 在屏幕上的位置
        PointType _w;  // SpinBox 的宽度
        PointType _h;  // SpinBox 的高度

        uint8_t _min = 0;
        uint8_t _max = 0;      // 当_list != nullptr 时，表示list 长度
        uint8_t _current = 0;  // 当_list != nullptr 时，表示当前选中的索引

        uint8_t _flag = 0;  // 标志位

        static constexpr uint8_t FLAG_INDEX_CHANGED = 0;   // 标志位，表示值已改变
        static constexpr uint8_t FLAG_INDEX_FOCUSED = 1;   // 标志位，表示SpinBox 已经获得焦点
        static constexpr uint8_t FLAG_INDEX_HEX = 2;       // 标志位，表示SpinBox 按16 进制显示数字
        static constexpr uint8_t FLAG_INDEX_NO_CYCLE = 3;  // 标志位，设为1 使inc 和dec 到达max 或min 时不自动循环
        static constexpr uint8_t FLAG_INDEX_NO_CARRY = 4;  // 标志位，设为1 禁止进位或借位


       protected:
        // 与this 链接的另一个box 发生上溢
        void linked_box_overflow() {
            inc();
        }

        // 与this 链接的另一个box 发生下溢
        void linked_box_underflow() {
            dec();
        }


       public:
        SpinBox() = default;


        SpinBox(ConstantStrArrayPtr list, uint8_t count, uint8_t current = 0) :
            _list(list), _min(0), _max(count), _current(current) {
            // 确保当前索引不越界
            if (_current >= _max) {
                _current = 0;
            }
        }


        /**
         * @brief 构造函数，创建一个SpinBox，指定最小值、最大值和当前值。
         *
         * @param min 最小值
         * @param max 最大值，取值范围：[min, max)
         * @param current 当前值，默认为0
         */
        SpinBox(uint8_t min, uint8_t max, uint8_t current = 0) :
            _list(nullptr), _min(min), _max(max), _current(current) {
            // 确保当前值在范围内
            if (_current < _min || _current > _max) {
                _current = min;
            }
        }


        /**
         * @brief 按从右到左的方向依次链接表示各位数字的SpinBox
         *
         * @param prev 指向表示左侧数字的SpinBox
         */
        void link(SpinBox* prev) {
            this->_next = prev;
        }


        void set_list(ConstantStrArrayPtr list, uint8_t count, uint8_t current = 0) {
            _list = list;
            _max = count;
            _min = 0;                                  // 当有选项列表时，最小值为0
            _current = current < count ? current : 0;  // 确保当前索引不越界
        }


        /**
         * @brief 设置取值范围：[min, max)
         *
         * @param min
         * @param max
         * @param current
         */
        void set_range(uint8_t min, uint8_t max) {
            _min = min;
            _max = max;
            _current = (_current < min || _current >= max) ? min : _current;  // 确保当前值在范围内
        }


        void set_range(uint8_t min, uint8_t max, uint8_t current) {
            _min = min;
            _max = max;
            _current = (current < min || current >= max) ? min : current;  // 确保当前值在范围内
        }


        /**
         * @brief 设置坐标和尺寸
         *
         * x,y 表示SpinBox 中的文本的起始坐标，按照U8g2 的坐标系，起始坐标是文本的基线，
         * 一般在文本的左下角。
         *
         * w,h 表示SpinBox 的宽度和高度，单位是像素。h 包括了文本和光标的高度，计算起点在SpinBox 的左上角。
         * w 是整体宽度，更新文本时，会自动清空屏幕上对应w,h 尺寸的一个矩形区域。
         *
         * 当h 较大时，底部光标和文本的垂直距离较远，反之则较近。h 过小时，光标会覆盖文本。
         * 光标高度2 像素，宽度等于w。
         *
         * @param x
         * @param y
         * @param w
         * @param h
         */
        void set_placing(PointType x, PointType y, PointType w, PointType h) {
            _x = x;
            _y = y;
            _w = w;
            _h = h;
            set_changed(true);  // 设置为已改变状态
        }


        uint8_t current() const {
            return _current;
        }


        const char* current_text() const {
            if (_list != nullptr && _current < _max) {
                return _list[_current];
            }
            return nullptr;  // 如果没有选项列表或索引越界，返回nullptr
        }


        const SpinBox* linked_box() const {
            return _next;
        }


        SpinBox* linked_box() {
            return _next;
        }


        bool is_option_list() const {
            return _list != nullptr;
        }


        bool is_range() const {
            return _list == nullptr;
        }


        void set_current(uint8_t value) {
            // 如果没有选项列表，则设置当前值
            if (value >= _min && value <= _max) {
                _current = value;
            }
        }


        bool changed() const {
            return (_flag & (1 << FLAG_INDEX_CHANGED)) != 0;
        }


        void set_changed(bool changed) {
            if (changed) {
                _flag |= (1 << FLAG_INDEX_CHANGED);
            }
            else {
                _flag &= ~(1 << FLAG_INDEX_CHANGED);
            }
        }


        bool focused() const {
            return (_flag & (1 << FLAG_INDEX_FOCUSED)) != 0;
        }


        void set_focused(bool focused) {
            if (focused) {
                _flag |= (1 << FLAG_INDEX_FOCUSED);
            }
            else {
                _flag &= ~(1 << FLAG_INDEX_FOCUSED);
            }
            set_changed(true);  // 设置为已改变状态
        }


        bool cycle() const {
            return (_flag & (1 << FLAG_INDEX_NO_CYCLE)) == 0;
        }


        void set_cycle(bool cycle) {
            if (!cycle) {
                _flag |= (1 << FLAG_INDEX_NO_CYCLE);
            }
            else {
                _flag &= ~(1 << FLAG_INDEX_NO_CYCLE);
            }
        }


        bool carry() const {
            return (_flag & (1 << FLAG_INDEX_NO_CARRY)) == 0;
        }


        void set_carry(bool c) {
            if (!c) {
                _flag |= (1 << FLAG_INDEX_NO_CARRY);
            }
            else {
                _flag &= ~(1 << FLAG_INDEX_NO_CARRY);
            }
        }


        bool is_hex() const {
            return (_flag & (1 << FLAG_INDEX_HEX)) != 0;
        }


        void set_hex(bool hex) {
            if (hex) {
                _flag |= (1 << FLAG_INDEX_HEX);
            }
            else {
                _flag &= ~(1 << FLAG_INDEX_HEX);
            }
            set_changed(true);  // 设置为已改变状态
        }


        void inc() {
            if (_current < _max - 1) {
                ++_current;
            }
            else if (cycle()) {
                _current = _min;  // 循环到第一个选项

                if (carry() && _next != nullptr) {
                    _next->linked_box_overflow();
                }
            }
            else {
                return;
            }

            set_changed(true);
        }


        void dec() {
            if (_current > _min) {
                --_current;
            }
            else if (cycle()) {
                _current = _max - 1;  // 循环到最后一个选项

                if (carry() && _next != nullptr) {
                    // TODO: 这个地方inc 和dec 函数都会递归执行，如果spin box 一串数量比较多，
                    // 可能会有问题。
                    _next->linked_box_underflow();
                }
            }
            else {
                return;
            }

            set_changed(true);
        }


        /**
         * @brief 汇总当前box 和所有prev box 的值，返回整体数值。
         *
         * 如果用一个spin box 表示一个数字的某一位，比如个位、十位、百位等，
         * 可以用这个函数方便获取整体数值。
         *
         * 应该调用最右侧的SpinBox，才能获取整行SpinBox 代表的数值。
         *
         * 如果spin box 有选项列表，同样按照选中的索引计算总和。这样可以用来一次获取一组选项的值。
         *
         * @param base 基数，默认为10，如果为0，则使用prev box 的最大值作为基数
         * @return uint32_t
         */
        uint32_t sum(uint8_t base = 10) const {
            uint32_t sum = current();
            uint32_t multiplier = 1;

            const SpinBox* prev = this->_next;
            while (prev != nullptr /*&& prev->is_range()*/) {
                auto b = (base > 0) ? base : prev->_max;  // 如果没有基数，则使用最大值
                multiplier *= b;
                sum += prev->current() * multiplier;
                prev = prev->_next;  // 移动到下一个box
            }

            return sum;
        }


        /**
         * @brief 按位分解总和，将值分配给当前box 和左侧所有prev box
         *
         * @param sum
         * @param base
         */
        void split_sum(uint32_t sum, uint8_t base = 10) {
            SpinBox* current = this;
            while (current != nullptr /*&& current->is_range()*/) {
                auto b = (base > 0) ? base : current->_max;  // 如果没有基数，则使用最大值
                auto r = static_cast<uint8_t>(sum % b);      // 计算余数
                sum /= b;
                r = constrain(r, current->_min, current->_max - 1);  // 确保值在范围内
                current->set_current(r);                             // 设置当前box 的值
                current = current->_next;                            // 移动到下一个box
            }

            set_changed(true);  // 设置为已改变状态
        }


        /**
         * @brief 在屏幕上绘制SpinBox
         *
         * @param is_initial 是否是初始绘制
         * @return true 绘制成功
         * @return false 没有变化，不需要重绘
         */
        bool draw(bool is_initial) {
            if (!is_initial && !changed() && _max > 1) {
                // 没有变化，不需要重绘
                return false;
            }

            set_changed(false);  // 重置

            u8g2.setFont(MENU_FONT);
            auto font_height = u8g2.getAscent();
            auto top_y = _y - font_height;  // 顶部Y坐标
            oled_clear_box(_x, top_y, _w, _h);
            u8g2.setCursor(_x, _y);

            if (_list == nullptr) {
                // 如果没有选项列表，直接显示当前值
                if (is_hex()) {
                    // u8g2.print("0x");
                    u8g2.print(_current, HEX);
                }
                else {
                    u8g2.print(_current);
                }
            }
            else {
                // 如果有选项列表，显示当前选中的选项
                if (_current < _max) {
                    u8g2.print(_list[_current]);
                }
                else {
                    u8g2.print("N/A");  // 如果索引越界，显示"N/A"
                }
            }

            auto bottom_y = top_y + _h;

            if (focused()) {
                u8g2.drawBox(_x, bottom_y - 2, _w, 2);  // 绘制底部的光标线
            }

            return true;  // 返回true表示SpinBox已成功绘制
        }


        uint8_t x() const {
            return _x;
        }


        uint8_t y() const {
            return _y;
        }


        uint8_t width() const {
            return _w;
        }


        uint8_t height() const {
            return _h;
        }
    };


    class SpinBoxGroup {
       private:
        SpinBox* _boxes = nullptr;  // SpinBox 数组

        uint8_t _count = 0;          // SpinBox 数量
        uint8_t _focused_index = 0;  // 当前获得焦点的SpinBox索引

        bool _focused = false;      // 整个group 是否获得焦点
        bool _should_free = false;  // 是否需要释放SpinBox数组内存


        // 链接SpinBox，形成一个链表结构
        void link_boxes() {
            if (_count == 0)
                return;

            for (uint8_t i = 0; i < (_count - 1); ++i) {
                _boxes[i].link(&_boxes[i + 1]);  // 链接SpinBox
            }
            _boxes[_count - 1].link(nullptr);  // 最后一个SpinBox没有下一个
            _boxes[0].set_focused(true);       // 默认第一个SpinBox获得焦点
        }


       public:
        SpinBoxGroup() = default;


        ~SpinBoxGroup() {
            free_boxes();  // 析构时释放内存
        }


        void init_boxes(SpinBox* boxes, uint8_t count) {
            free_boxes();  // 释放之前的内存
            _boxes = boxes;
            _count = count;
            link_boxes();          // 链接SpinBox
            _should_free = false;  // 不需要释放内存
        }


        void allocate_boxes(uint8_t count) {
            free_boxes();  // 释放之前的内存
            _boxes = new SpinBox[count];
            _count = count;
            _should_free = true;
            link_boxes();
        }


        void free_boxes() {
            if (_should_free && _boxes != nullptr) {
                delete[] _boxes;  // 释放之前的内存
                _should_free = false;
            }
            _boxes = nullptr;  // 清空指针
            _count = 0;
        }


        void set_range(uint8_t min, uint8_t max, uint8_t current = 0) {
            if (_boxes != nullptr && _count > 0) {
                for (size_t i = 0; i < _count; ++i) {
                    SpinBox& b = _boxes[i];
                    b.set_range(min, max, current);  // 设置每个SpinBox的范围
                }
            }
        }


        void set_range(uint8_t index, uint8_t min, uint8_t max, uint8_t current = 0) {
            if (_boxes != nullptr && index < _count) {
                SpinBox& b = _boxes[index];
                b.set_range(min, max, current);  // 设置指定SpinBox的范围
            }
        }


        void set_list(ConstantStrArrayPtr list, uint8_t count, uint8_t current = 0) {
            if (_boxes != nullptr && _count > 0) {
                for (size_t i = 0; i < _count; ++i) {
                    SpinBox& b = _boxes[i];
                    b.set_list(list, count, current);  // 设置每个SpinBox的选项列表
                }
            }
        }


        void set_list(uint8_t index, ConstantStrArrayPtr list, uint8_t count, uint8_t current = 0) {
            if (_boxes != nullptr && index < _count) {
                SpinBox& b = _boxes[index];
                b.set_list(list, count, current);  // 设置指定SpinBox的选项列表
            }
        }


        const SpinBox* boxes() const {
            return _boxes;  // 返回SpinBox数组
        }


        SpinBox* box(uint8_t index) {
            if (index < _count) {
                return &_boxes[index];  // 返回指定索引的SpinBox
            }
            return nullptr;  // 如果索引越界，返回nullptr
        }


        uint8_t count() const {
            return _count;  // 返回SpinBox数量
        }


        void set_not_focused() {
            for (size_t i = 0; i < _count; ++i) {
                _boxes[i].set_focused(false);  // 设置所有SpinBox为未聚焦状态
            }
            _focused = false;  // 整个group 失去焦点
        }


        void set_focused(uint8_t index) {
            if (index < _count) {
                if (_boxes[index].focused()) {
                    // 如果当前SpinBox已经聚焦，则不需要重复设置
                    return;
                }

                _boxes[_focused_index].set_focused(false);  // 取消之前聚焦的SpinBox
                _boxes[index].set_focused(true);            // 设置指定SpinBox为聚焦状态
                _focused_index = index;                     // 更新当前聚焦的索引
                _focused = true;                            // 整个group 获得焦点
            }
        }


        void set_focused() {
            set_focused(_focused_index);  // 设置当前聚焦的SpinBox
        }


        bool focus_left() {
            if (_count == 0)
                return false;
            if (_focused_index < _count - 1) {
                set_focused(_focused_index + 1);  // 聚焦下一个SpinBox
                return false;
            }
            else {
                set_focused(0);  // 如果已经是最后一个，则回到第一个
                return true;
            }
        }


        bool focus_right() {
            if (_count == 0)
                return false;
            if (_focused_index > 0) {
                set_focused(_focused_index - 1);  // 聚焦上一个SpinBox
                return false;
            }
            else {
                set_focused(_count - 1);  // 如果已经是第一个，则回到最后一个
                return true;
            }
        }


        void inc() {
            if (_focused_index >= _count)
                return;

            SpinBox& current_box = _boxes[_focused_index];
            current_box.inc();  // 增加当前聚焦的SpinBox的值
        }


        void dec() {
            if (_focused_index >= _count)
                return;

            SpinBox& current_box = _boxes[_focused_index];
            current_box.dec();  // 减少当前聚焦的SpinBox的值
        }


        uint32_t sum(uint8_t base = 10) const {
            if (_count == 0)
                return 0;

            return _boxes[0].sum(base);  // 返回组内SpinBox的总和
        }


        void split_sum(uint32_t sum, uint8_t base = 10) {
            if (_count == 0)
                return;

            _boxes[0].split_sum(sum, base);  // 将总和分配给组内SpinBox
        }


        /**
         * @brief 设置每个box 的位置和尺寸
         *
         * @param x  最左侧spin box 的起始x 坐标
         * @param y  最左侧spin box 的起始y 坐标
         * @param w
         * @param h
         * @param spacing  间隔
         * @return uint8_t
         */
        PointType set_placing(PointType x, PointType y, PointType w, PointType h, PointType spacing = 0) {
            if (_boxes != nullptr && _count > 0) {
                for (int i = _count - 1; i > -1; --i) {
                    SpinBox& b = _boxes[i];
                    b.set_placing(x, y, w, h);  // 设置每个SpinBox的位置和尺寸
                    x += w + spacing;           // 更新下一个SpinBox的x坐标
                }
            }

            return x - spacing;  // 返回最右侧SpinBox显示区域的右边界X 坐标
        }


        void set_hex(bool hex) {
            if (_boxes != nullptr && _count > 0) {
                for (size_t i = 0; i < _count; ++i) {
                    SpinBox& b = _boxes[i];
                    b.set_hex(hex);  // 设置每个SpinBox是否按16进制显示
                }
            }
        }


        bool draw(bool is_initial) {
            if (_boxes == nullptr || _count == 0) {
                return false;  // 如果没有SpinBox，返回false
            }

            bool changed = false;
            for (size_t i = 0; i < _count; ++i) {
                SpinBox& box = _boxes[i];
                if (box.draw(is_initial)) {
                    changed = true;  // 如果有任何一个SpinBox绘制成功，则返回true
                }
            }

            return changed;  // 返回是否有任何SpinBox绘制成功
        }
    };

}  // namespace beef